// Copyright DustedPixels.com 2008. All rights reserved.

package com.dustedpixels.jasmin.unit.z80.v3;

import com.dustedpixels.jasmin.unit.Unit;
import com.dustedpixels.jasmin.unit.tools.Oscilloscope;
import com.dustedpixels.jasmin.unit.tools.Pin;

/**
 * Cycle exact Z80 implementation.
 * 
 * TODO(micapolos): Things to implements
 * - pins: WAIT, BUSRQ, BUSACK
 * 
 * @author micapolos@gmail.com (Michal Pociecha-Los)
 */
public final class Z80 implements Unit, Constants {
  // Public signals
  public boolean CLK;
  public boolean RESET;
  public int ADDR;
  public int DATA;
  public boolean DATAZ;
  
  public boolean M1;
  public boolean RFSH;
  public boolean MREQ;
  public boolean IORQ;
  public boolean RD;
  public boolean WR;

  // CLK from previous 'updateEndPoints' call, to detect signal edge.
  private boolean lastClk;
  
  private int dataBuffer; // Data bus buffer
  private int addrBuffer; // Address bus buffer
  
  private int opcode; // Machine instruction
  private int instr;  // Micro operation
  
  private int xyMode;   // HL, IX or IY
  private int instrSet; // base, CB, ED

  // Machine cycle handling
  private boolean nextMCycle;
  private boolean nextMCycleFetch;
  private boolean nextMCycleWrite;
  private boolean nextMCycleIo;

  private boolean mCycleFetch;
  private boolean mCycleWrite;  // Write cycle (mem or IO)
  private boolean mCycleIo;     // IO cycle
  private boolean insideMCycle;
  private boolean mCycleWait;
  private int tCycle;          // Clock cycle number within machine cycle. 0 = T1, 1 = T2, etc...
  private boolean fetchExecuteOverlap;
  
  private int nextAddrBusIn;
  private int nextDataBusIn;
  private boolean nextDataBusSpMode;
  
  // Indicates machine cycle, that new value for PC register is stored in WZ register.
  private boolean replacePcWz;
  
  // Instruction handling
  private boolean insideInstr;
  private int iCycle;
  private boolean iCycleWait;

  // Registers
  private int b,  c,  d,  e,  hl,  acc,  flags;
  private int b2, c2, d2, e2, hl2, acc2, flags2;
  private int ix, iy;
  private int pc, sp;
  private int ir;
  private int tmpData, tmpAddr, tmpAcc;
  private int wz;
  private int im;
  private boolean iff1, iff2;
  private boolean decodedCc;
  
  // Control registers updated on rising clock edge.
  private boolean m1;
  private boolean rfsh;
  private boolean mreq;
  private boolean rd;
  private boolean wr;
  
  // Control registers updated on falling clock edge.
  private boolean mreqAlt;
  private boolean rdAlt;
  private boolean wrAlt;
  private boolean dataZ;
  private boolean dataZAlt;
  
  public void updateEndPoints() {
    // Local variable used to implement register exchange instructions
    int tmp; 
    
    if (CLK != lastClk) {
      if (CLK) {
        // Sequential part (update registers).
        if (RESET) {
          b = 0xFF;
          c = 0xFF;
          d = 0xFF;
          e = 0xFF;
          hl = 0xFFFF;
          acc = 0xFF;
          flags = 0xFF;
          
          b2 = 0xFF;
          c2 = 0xFF;
          d2 = 0xFF;
          e2 = 0xFF;
          hl2 = 0xFFFF;
          acc2 = 0xFF;
          flags2 = 0xFF;
                   
          ix = 0xFFFF;
          iy = 0xFFFF;
          
          pc = 0;
          sp = 0xFFFF;
          
          ir = 0xFFFF;
          
          tmpData = 0xFF;
          tmpAddr = 0xFFFF;
          
          wz = 0xFFFF;
          im = 0;
          iff1 = false;
          iff2 = false;
          
          decodedCc = false;

          xyMode = XY_MODE_HL;
          instrSet = INSTR_SET_BASE;
          
          mCycleFetch = false;
          mCycleWrite = false;
          mCycleIo = false;
          tCycle = 0;
          
          insideMCycle = false;
          nextMCycle = true;
          nextMCycleFetch = true;
          replacePcWz = false;
          
          insideInstr = false;
          fetchExecuteOverlap = false;
          
          m1 = false;
          rfsh = false;
          mreq = false;
          mreqAlt = false;
          rd = false;
          rdAlt = false;
          wr = false;
          wrAlt = false;
          dataZ = true;
          dataZAlt = true;
        } else { // if (!RESET)
          // Internal bus control signals
          int dataBus = 0;
          int addrBus = 0;
          int addrBusIn = ADDR_IN_ZERO;
          int addrBusOut = ADDR_OUT_NONE;
          int dataBusIn = DATA_IN_ZERO;
          int dataBusOut = DATA_OUT_NONE;
          boolean dataBusSpMode = false;
          int addrBusIncMode = ADDR_INC_MODE_NONE;
          int addrBusInc;
          
          boolean aluEnable = false;
          int aluOp = 0;
          boolean aluZero = false;
          int aluOut = 0;
          int aluFlagsMask = 0xFF;
          
          boolean decodeCc = false;
          int cc = 0;
          
          // Internal instruction processing signals
          boolean decodeInstr = false;
          boolean processInstr = false;
          boolean processInstrOverlap = false;
          
          // Main machine loop
          if (insideMCycle) {
            tCycle++;
          } else if (nextMCycle) {
            insideMCycle = true;
            mCycleFetch = nextMCycleFetch;
            mCycleWrite = nextMCycleWrite;
            mCycleIo = nextMCycleIo;
            tCycle = 0;
            
            nextMCycle = false;
            nextMCycleFetch = false;
            nextMCycleWrite = false;
            nextMCycleIo = false;
            
            // Reset data active signal on every MCycle start.
            m1 = false;
            rfsh = false;
            mreq = false;
            mreqAlt = true;
            rd = false;
            rdAlt = true;
            wr = false;
            wrAlt = false;
            dataZ = true;
            dataZAlt = true;
          }

          if (insideMCycle) {
            if (mCycleFetch) {
              switch (tCycle) {
                case 0:
                  // Place PC on address bus (exceptionally WZ)
                  addrBusIn = replacePcWz ? ADDR_IN_REG_WZ : ADDR_IN_REG_PC;
                  addrBusOut = ADDR_OUT_BUFFER;
                  m1 = true;
                  break;
                case 1:
                  // Increment PC (exceptionally put WZ+1 into PC)
                  addrBusIncMode = ADDR_INC_MODE_INC;
                  addrBusIn = replacePcWz ? ADDR_IN_REG_WZ : ADDR_IN_REG_PC;
                  addrBusOut = ADDR_OUT_REG_PC;
                  replacePcWz = false;
                  // Increment R register.
                  // TODO(micapolos): DDCBxxxx and DDEDxxxx instruction should increment it 
                  // by 2, and not 3. Additional check is needed here.
                  ir = (ir & 0xFF80) | ((ir + 1) & 0x7F);
                  mreq = true;
                  rd = true;
                  processInstrOverlap = true;
                  break;
                case 2:
                  addrBusIn = ADDR_IN_REG_IR;
                  addrBusOut = ADDR_OUT_BUFFER;
                  m1 = false;
                  rfsh = true;
                  mreq = false;
                  rd = false;
                  rdAlt = false;
                  decodeInstr = true;
                  break;
                case 3:
                  mreq = true;
                  mreqAlt = false;
                  insideMCycle = false;
                  processInstr = true;
                  break;
              } // switch (tCycle)
            } else { // !mCycleFetch
              switch (tCycle) {
                case 0:
                  addrBusIn = nextAddrBusIn;
                  addrBusOut = ADDR_OUT_BUFFER;
                  if (mCycleWrite) {
                    dataBusIn = nextDataBusIn;
                    dataBusOut = DATA_OUT_BUFFER;
                    dataBusSpMode = nextDataBusSpMode;
                  }
                  mreqAlt = true;
                  rdAlt = !mCycleWrite;
                  mCycleWait = true;
                  dataZAlt = !mCycleWrite;
                  break;
                case 1:
                  mreq = true;
                  rd = !mCycleWrite;
                  wrAlt = mCycleWrite;
                  dataZ = !mCycleWrite;
                  processInstr = true;
                  break;
                case 2:
                  mreqAlt = false;
                  rdAlt = false;
                  wr = mCycleWrite;
                  wrAlt = false;
                  mCycleWait = false;
                  insideMCycle = false;
                  processInstr = true;
                  break;
              }
            } // if (fetchCycle)
          } else { // !insideMCycle
            processInstr = true;
          }
          
          // Decode machine instruction from data bus.
          if (decodeInstr) {
            instr = MOP_NOP;
            opcode = DATA;
            fetchExecuteOverlap = false;
            
            switch (instrSet) {
              case INSTR_SET_BASE:
                switch (DATA) {
                  case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x47:
                  case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4F:
                  case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x57:
                  case 0x58: case 0x59: case 0x5A: case 0x5B: case 0x5C: case 0x5D: case 0x5F:
                  case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x67:
                  case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6F:
                  case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7F:
                    instr = MOP_LD_r_r; 
                    break;
                  case 0x06: case 0x0E: case 0x16: case 0x1E: case 0x26: case 0x2E: case 0x3E:
                    instr = MOP_LD_r_n;
                    break;
                  case 0x46: case 0x4E: case 0x56: case 0x5E: case 0x66: case 0x6E: case 0x7E:
                    instr = MOP_LD_r_iHL;
                    break;
                  case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x77:
                    instr = MOP_LD_iHL_r;
                    break;
                  case 0x36:
                    instr = MOP_LD_iHL_n;
                    break;
                  case 0x0A: case 0x1A:
                    instr = MOP_LD_A_idd;
                    break;
                  case 0x3A:
                    instr = MOP_LD_A_inn;
                    break;
                  case 0x02: case 0x12:
                    instr = MOP_LD_idd_A;
                    break;
                  case 0x32:
                    instr = MOP_LD_inn_A;
                    break;
                  case 0x01: case 0x11: case 0x21: case 0x31:
                    instr = MOP_LD_dd_nn;
                    break;
                  case 0x22:
                    instr = MOP_LD_inn_dd;
                    break;
                  case 0x2A:
                    instr = MOP_LD_dd_inn;
                    break;
                  case 0xF9:
                    instr = MOP_LD_SP_HL;
                    break;
                  case 0xC5: case 0xD5: case 0xE5: case 0xF5:
                    instr = MOP_PUSH_qq;
                    break;
                  case 0xC1: case 0xD1: case 0xE1: case 0xF1:
                    instr = MOP_POP_qq;
                    break;
                  case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x87:
                  case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8F:
                  case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x97:
                  case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E:
                  case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA7:
                  case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE:
                  case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB7:
                  case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBF:
                    instr = MOP_ALU_r;
                    break;
                  case 0x04: case 0x0C: case 0x14: case 0x1C: case 0x24: case 0x2C: case 0x3C:
                    instr = MOP_INC_r;
                    break;
                  case 0x05: case 0x0D: case 0x15: case 0x1D: case 0x25: case 0x2D: case 0x3D:
                    instr = MOP_DEC_r;
                    break;
                  case 0xC3:
                    instr = MOP_JP_nn;
                    break;
                  case 0xC2: case 0xCA: case 0xD2: case 0xDA: case 0xE2: case 0xEA: case 0xF2: case 0xFA: 
                    instr = MOP_JP_cc_nn;
                    break;
                  case 0x18:
                    instr = MOP_JR_e;
                    break;
                  case 0xCB:
                    instr = MOP_PREFIX_CB;
                    break;
                  case 0xED:
                    instr = MOP_PREFIX_ED;
                    break;
                  case 0xDD:
                    instr = MOP_PREFIX_DD;
                    break;
                  case 0xFD:
                    instr = MOP_PREFIX_FD;
                    break;
                }
                break;
              case INSTR_SET_CB:
                switch (DATA) {
                  
                }
                instrSet = INSTR_SET_BASE;
                break;
              case INSTR_SET_ED:
                switch (DATA) {
                  case 0x4B: case 0x5B: case 0x6B: case 0x7B:
                    instr = MOP_LD_dd_inn;
                    break;
                  case 0x43: case 0x53: case 0x63: case 0x73:
                    instr = MOP_LD_inn_dd;
                    break;
                  case 0x57:
                    instr = MOP_LD_A_I;
                    break;
                  case 0x5F:
                    instr = MOP_LD_A_R;
                    break;
                  case 0x47:
                    instr = MOP_LD_I_A;
                    break;
                  case 0x4F:
                    instr = MOP_LD_R_A;
                    break;
                }
                instrSet = INSTR_SET_BASE;
                break;
            }
            
            insideInstr = true;
            iCycle = 0;
          }
          
          // Decode micro instructions from machine instruction
          if ((insideInstr && processInstr) || (fetchExecuteOverlap && processInstrOverlap)) {
            if (!mCycleWait || iCycleWait) {
              switch (instr) {
                case MOP_LD_r_r:
                  switch (iCycle) {
                    case 0:
                      dataBusIn = opcode & 0x07;
                      dataBusOut = (opcode >>> 3) & 0x07;
                      insideInstr = false;
                      break;
                  }
                  break;
                case MOP_LD_r_n:
                  switch (iCycle) {
                    case 0:
                      nextAddrBusIn = ADDR_IN_REG_PC;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 1:
                      addrBusIn = ADDR_IN_REG_PC;
                      addrBusOut = ADDR_OUT_REG_PC;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 2:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = (opcode >>> 3) & 0x07;
                      insideInstr = false;
                      break;
                  }
                  break;
                case MOP_LD_r_iHL:
                  switch (iCycle) {
                    case 0:
                      nextAddrBusIn = ADDR_IN_REG_HL;
                      nextMCycle = true;
                      break;
                    case 1:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = (opcode >>> 3) & 0x07;
                      insideInstr = false;
                      break;
                  }
                  break;        
                case MOP_LD_iHL_r:
                  switch (iCycle) {
                    case 0:
                      nextAddrBusIn = ADDR_IN_REG_HL;
                      nextDataBusIn = opcode & 0x07;
                      nextMCycle = true;
                      nextMCycleWrite = true;
                      break;
                    case 1:
                      insideInstr = false;
                      break;
                  }
                  break;
                case MOP_LD_iHL_n:
                  switch (iCycle) {
                    case 0:
                      nextAddrBusIn = ADDR_IN_REG_PC;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 1:
                      addrBusIn = ADDR_IN_REG_PC;
                      addrBusOut = ADDR_OUT_REG_PC;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 2:
                      nextAddrBusIn = ADDR_IN_REG_HL;
                      nextDataBusIn = DATA_IN_REG_TMP;
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = DATA_OUT_REG_TMP;
                      nextMCycle = true;
                      nextMCycleWrite = true;
                      break;
                    case 3:
                      insideInstr = false;
                      break;
                  }
                  break;
                case MOP_LD_A_idd:
                  switch (iCycle) {
                    case 0:
                      nextAddrBusIn = (opcode >>> 4) & 0x03;
                      nextMCycle = true;
                      break;
                    case 1:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = DATA_IN_REG_A;
                      insideInstr = false;
                      break;
                  }
                  break;
                case MOP_LD_A_inn:
                  switch (iCycle) {
                    case 0:
                      nextAddrBusIn = ADDR_IN_REG_PC;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 1:
                      addrBusIn = ADDR_IN_REG_PC;
                      addrBusOut = ADDR_OUT_REG_PC;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 2:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = DATA_OUT_REG_Z;
                      nextAddrBusIn = ADDR_IN_REG_PC;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 3:
                      addrBusIn = ADDR_IN_REG_PC;
                      addrBusOut = ADDR_OUT_REG_PC;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 4:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = DATA_IN_REG_W;
                      nextAddrBusIn = ADDR_IN_REG_WZ;
                      nextMCycle = true;
                      break;
                    case 5:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = DATA_IN_REG_A;
                      insideInstr = false;
                      break;
                  }
                  break;
                case MOP_LD_idd_A:
                  switch (iCycle) {
                    case 0:
                      nextDataBusIn = DATA_IN_REG_A;
                      nextAddrBusIn = (opcode >>>4) & 0x03;
                      nextMCycle = true;
                      nextMCycleWrite = true;
                      break;
                    case 1:
                      insideInstr = false;
                      break;
                  }
                  break;
                case MOP_LD_inn_A:
                  switch (iCycle) {
                    case 0:
                      nextAddrBusIn = ADDR_IN_REG_PC;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 1:
                      addrBusIn = ADDR_IN_REG_PC;
                      addrBusOut = ADDR_OUT_REG_PC;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 2:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = DATA_OUT_REG_Z;
                      nextAddrBusIn = ADDR_IN_REG_PC;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 3:
                      addrBusIn = ADDR_IN_REG_PC;
                      addrBusOut = ADDR_OUT_REG_PC;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 4:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = DATA_OUT_REG_W;
                      nextAddrBusIn = ADDR_IN_REG_WZ;
                      nextDataBusIn = DATA_IN_REG_A;
                      nextMCycle = true;
                      nextMCycleWrite = true;
                      break;
                    case 5:
                      insideInstr = false;
                      break;
                  }
                case MOP_LD_A_I:
                case MOP_LD_A_R:
                case MOP_LD_I_A:
                case MOP_LD_R_A:
                  // TODO
                  break;
                case MOP_LD_dd_nn:
                  switch (iCycle) {
                    case 0:
                      nextAddrBusIn = ADDR_IN_REG_PC;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 1:
                      addrBusIn = ADDR_IN_REG_PC;
                      addrBusOut = ADDR_OUT_REG_PC;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 2:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = (opcode >>> 3) & 0x06 | 0x01;
                      dataBusSpMode = true;
                      nextAddrBusIn = ADDR_IN_REG_PC;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 3:
                      addrBusIn = ADDR_IN_REG_PC;
                      addrBusOut = ADDR_OUT_REG_PC;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 4:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = (opcode >>> 3) & 0x06;
                      dataBusSpMode = true;
                      insideInstr = false;
                      break;
                  }
                  break;
                case MOP_LD_dd_inn:
                  switch (iCycle) {
                    case 0:
                      nextAddrBusIn = ADDR_IN_REG_PC;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 1:
                      addrBusIn = ADDR_IN_REG_PC;
                      addrBusOut = ADDR_OUT_REG_PC;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 2:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = DATA_OUT_REG_Z;
                      nextAddrBusIn = ADDR_IN_REG_PC;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 3:
                      addrBusIn = ADDR_IN_REG_PC;
                      addrBusOut = ADDR_OUT_REG_PC;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 4:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = DATA_OUT_REG_W;
                      nextAddrBusIn = ADDR_IN_REG_WZ;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 5:
                      addrBusIn = ADDR_IN_REG_WZ;
                      addrBusOut = ADDR_OUT_REG_WZ;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 6:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = (opcode >>> 3) & 0x06 | 0x01;
                      dataBusSpMode = true;
                      nextAddrBusIn = ADDR_IN_REG_WZ;
                      nextMCycle = true;
                      break;
                    case 7:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = (opcode >>> 3) & 0x06;
                      dataBusSpMode = true;
                      insideInstr = false;
                      break;
                  }
                  break;
                case MOP_LD_inn_dd:
                  switch (iCycle) {
                    case 0:
                      nextAddrBusIn = ADDR_IN_REG_PC;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 1:
                      addrBusIn = ADDR_IN_REG_PC;
                      addrBusOut = ADDR_OUT_REG_PC;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 2:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = DATA_OUT_REG_Z;
                      nextAddrBusIn = ADDR_IN_REG_PC;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 3:
                      addrBusIn = ADDR_IN_REG_PC;
                      addrBusOut = ADDR_OUT_REG_PC;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 4:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = DATA_OUT_REG_W;
                      nextAddrBusIn = ADDR_IN_REG_WZ;
                      nextDataBusIn = (opcode >>> 3) & 0x06 | 0x01;
                      nextDataBusSpMode = true;
                      nextMCycle = true;
                      nextMCycleWrite = true;
                      iCycleWait = true;
                      break;
                    case 5:
                      addrBusIn = ADDR_IN_REG_WZ;
                      addrBusOut = ADDR_OUT_REG_WZ;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 6:
                      nextAddrBusIn = ADDR_IN_REG_WZ;
                      nextDataBusIn = (opcode >>> 3) & 0x06;
                      nextDataBusSpMode = true;
                      nextMCycle = true;
                      nextMCycleWrite = true;
                      break;
                    case 7:
                      insideInstr = false;
                      break;
                  }
                  break;
                case MOP_LD_SP_HL:
                  switch (iCycle) {
                    case 0:
                      addrBusIn = ADDR_IN_REG_HL;
                      addrBusOut = ADDR_OUT_REG_SP;
                      break;
                    case 1:
                      break;
                    case 2:
                      insideInstr = false;
                      break;
                  }
                  break;
                case MOP_PUSH_qq:
                  switch (iCycle) {
                    case 0:
                      addrBusIn = ADDR_IN_REG_SP;
                      addrBusOut = ADDR_OUT_REG_SP;
                      addrBusIncMode = ADDR_INC_MODE_DEC;
                      break;
                    case 1:
                      nextAddrBusIn = ADDR_IN_REG_SP;
                      nextDataBusIn = (opcode >>> 3) & 0x06 ^ ((opcode & 0x30) == 0x30 ? 1 : 0);
                      nextMCycle = true;
                      nextMCycleWrite = true;
                      iCycleWait = true;
                      break;
                    case 2:
                      addrBusIn = ADDR_IN_REG_SP;
                      addrBusOut = ADDR_OUT_REG_SP;
                      addrBusIncMode = ADDR_INC_MODE_DEC;
                      iCycleWait = false;
                      break;
                    case 3:
                      nextAddrBusIn = ADDR_IN_REG_SP;
                      nextDataBusIn = (opcode >>> 3) & 0x06 | 0x01 ^ ((opcode & 0x30) == 0x30 ? 1 : 0);
                      nextMCycle = true;
                      nextMCycleWrite = true;
                      break;
                    case 4:
                      insideInstr = false;
                      break;
                  }
                  break;
                case MOP_POP_qq:
                  switch (iCycle) {
                    case 0:
                      nextAddrBusIn = ADDR_IN_REG_SP;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 1:
                      addrBusIn = ADDR_IN_REG_SP;
                      addrBusOut = ADDR_OUT_REG_SP;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 2:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = (opcode >>> 3) & 0x06 | 0x01 ^ ((opcode & 0x30) == 0x30 ? 1 : 0);
                      nextAddrBusIn = ADDR_IN_REG_SP;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 3:
                      addrBusIn = ADDR_IN_REG_SP;
                      addrBusOut = ADDR_OUT_REG_SP;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 4:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = (opcode >>> 3) & 0x06 ^ ((opcode & 0x30) == 0x30 ? 1 : 0);
                      insideInstr = false;
                  }
                  break;
                case MOP_ALU_r:
                  switch (iCycle) {
                    case 0:
                      tmpAcc = acc;
                      dataBusIn = opcode & 0x07;
                      dataBusOut = DATA_OUT_REG_TMP;
                      insideInstr = false;
                      fetchExecuteOverlap = true;
                      break;
                    case 1:
                      aluEnable = true;
                      aluOp = (opcode >>> 3) & 0x07;
                      if (aluOp != 0x07) {
                        dataBusIn = DATA_IN_ALU;
                        dataBusOut = DATA_OUT_REG_A;
                      }
                      break;
                  }
                  break;
                case MOP_INC_r:
                  switch (iCycle) {
                    case 0:
                      dataBusIn = (opcode >>> 3) & 0x07;
                      dataBusOut = DATA_OUT_REG_TMP_ACC;
                      insideInstr = false;
                      fetchExecuteOverlap = true;
                      break;
                    case 1:
                      aluEnable = true;
                      aluOp = ALU_ADC;
                      aluZero = true;
                      aluFlagsMask = 0xFE;
                      dataBusIn = DATA_IN_ALU;
                      dataBusOut = (opcode >>> 3) & 0x07;
                      break;
                  }
                  break;
                case MOP_DEC_r:
                  switch (iCycle) {
                    case 0:
                      dataBusIn = (opcode >>> 3) & 0x07;
                      dataBusOut = DATA_OUT_REG_TMP_ACC;
                      insideInstr = false;
                      fetchExecuteOverlap = true;
                      break;
                    case 1:
                      aluEnable = true;
                      aluOp = ALU_SBC;
                      aluZero = true;
                      aluFlagsMask = 0xFE;
                      dataBusIn = DATA_IN_ALU;
                      dataBusOut = (opcode >>> 3) & 0x07;
                      break;
                  }
                  break;
                case MOP_EXX:
                  switch (iCycle) {
                    case 0:
                      tmp = b; b = b2; b2 = tmp; 
                      tmp = c; c = c2; c2 = tmp; 
                      tmp = d; d = d2; d2 = tmp; 
                      tmp = e; e = e2; e2 = tmp; 
                      tmp = hl; hl = hl2; hl2 = tmp;
                      insideInstr = false;
                      break;
                  }
                  break;
                case MOP_EX_AF_AF2:
                  switch (iCycle) {
                    case 0:
                      tmp = acc; acc = acc2; acc2 = acc;
                      tmp = flags; flags = flags2; flags2 = flags;
                      insideInstr = false;
                      break;
                  }
                  break;
                case MOP_JP_nn:
                  switch (iCycle) {
                    case 0:
                      nextAddrBusIn = ADDR_IN_REG_PC;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 1:
                      addrBusIn = ADDR_IN_REG_PC;
                      addrBusOut = ADDR_OUT_REG_PC;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 2:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = DATA_OUT_REG_Z;
                      nextAddrBusIn = ADDR_IN_REG_PC;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 3:
                      addrBusIn = ADDR_IN_REG_PC;
                      addrBusOut = ADDR_OUT_REG_PC;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 4:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = DATA_OUT_REG_W;
                      replacePcWz = true;
                      insideInstr = false;
                      break;
                  }
                  break;
                case MOP_JP_cc_nn:
                  switch (iCycle) {
                    case 0:
                      nextAddrBusIn = ADDR_IN_REG_PC;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 1:
                      addrBusIn = ADDR_IN_REG_PC;
                      addrBusOut = ADDR_OUT_REG_PC;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 2:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = DATA_OUT_REG_Z;
                      nextAddrBusIn = ADDR_IN_REG_PC;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 3:
                      addrBusIn = ADDR_IN_REG_PC;
                      addrBusOut = ADDR_OUT_REG_PC;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      cc = (opcode >>> 3) & 0x07;
                      decodeCc = true;
                      iCycleWait = false;
                      break;
                    case 4:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = DATA_OUT_REG_W;
                      replacePcWz = decodedCc;
                      insideInstr = false;
                      break;
                  }
                  break;
                case MOP_JR_e:
                  switch (iCycle) {
                    case 0:
                      addrBusIn = ADDR_IN_REG_PC;
                      addrBusOut = ADDR_OUT_REG_WZ;
                      nextAddrBusIn = ADDR_IN_REG_PC;
                      nextMCycle = true;
                      iCycleWait = true;
                      break;
                    case 1:
                      addrBusIn = ADDR_IN_REG_PC;
                      addrBusOut = ADDR_OUT_REG_PC;
                      addrBusIncMode = ADDR_INC_MODE_INC;
                      iCycleWait = false;
                      break;
                    case 2:
                      dataBusIn = DATA_IN_DATA;
                      dataBusOut = DATA_OUT_REG_TMP;
                      nextMCycle = true;
                      break;
                    case 3:
                      
                  }
                  break;
                case MOP_PREFIX_CB:
                  instrSet = INSTR_SET_CB;
                  insideInstr = false;
                  break;
                case MOP_PREFIX_DD:
                  xyMode = XY_MODE_IX;
                  insideInstr = false;
                  break;
                case MOP_PREFIX_ED:
                  instrSet = INSTR_SET_ED;
                  insideInstr = false;
                  break;
                case MOP_PREFIX_FD:
                  xyMode = XY_MODE_IY;
                  insideInstr = false;
                  break;
              } // switch (instr)
              
              if (!insideInstr && !processInstrOverlap) {
                nextMCycle = true;
                nextMCycleFetch = true;
              }
              
              iCycle++;
            } // if (!mCycleWait || iCycleWait)
          } // if (processMop && insideInstr)
          
          // ALU
          if (aluEnable) {
            int l = tmpAcc;
            int r = aluZero ? 0 : tmpData;
            int res, resFlags;
            if ((aluOp & 0x04) == 0 || (aluOp == 0x07)) {
              // ADD, ADC, SUB, SBC and CP
              if (((aluOp & 0x01) != 0) && (aluOp != 0x07)) r++;
              int res4, res7;
              if ((aluOp & 0x02) == 0) {
                res4 = (l & 0x0F) + (r & 0x0F); 
                res7 = (l & 0x7F) + (r & 0x7F);
                res = (l & 0xFF) + (r & 0xFF);
              } else {
                res4 = (l & 0x0F) - (r & 0x0F); 
                res7 = (l & 0x7F) - (r & 0x7F);
                res = (l & 0xFF) - (r & 0xFF);
              }
              resFlags = 
                  (res & 0x80) | 
                  (((res & 0xFF) == 0) ? 0x40 : 0x00) |
                  (res & 0x20) |
                  (res4 & 0x10) |
                  (res & 0x08) |
                  ((((aluOp << 1) ^ ((~l ^ r) >>> 5)) & ((l ^ res) >>> 5)) & 0x04) |
                  (aluOp & 0x02) |
                  (res >>> 8) & 0x01;
            } else {
              switch (aluOp) {
                case ALU_AND: res = l & r; break;
                case ALU_OR:  res = l | r; break;
                case ALU_XOR: res = l ^ r; break;
                default: res = 0;
              }
              resFlags = 
                  (res & 0x80) |
                  (((res & 0xFF) == 0) ? 0x40 : 0x00) |
                  (res & 0x20) |
                  ((aluOp == ALU_AND) ? 0x10 : 0x00)|
                  (res & 0x08) |
                  (((Integer.bitCount(res & 0xFF) & 0x01) == 0) ? 0x04 : 0x00) |
                  0x00 |
                  0x00;
            }
            
            aluOut = res;
            
            flags = (flags & ~aluFlagsMask) | (resFlags & aluFlagsMask);
          }
          
          if (decodeCc) {
            switch (cc) {
              case CC_NZ: decodedCc = (flags & FLAGS_Z)  == 0; break;
              case CC_Z:  decodedCc = (flags & FLAGS_Z)  != 0; break;
              case CC_NC: decodedCc = (flags & FLAGS_C)  == 0; break;
              case CC_C:  decodedCc = (flags & FLAGS_C)  != 0; break;
              case CC_PO: decodedCc = (flags & FLAGS_PV) == 0; break;
              case CC_PE: decodedCc = (flags & FLAGS_PV) != 0; break;
              case CC_P:  decodedCc = (flags & FLAGS_S)  == 0; break;
              case CC_M:  decodedCc = (flags & FLAGS_S)  != 0; break;
            }
          }
            
          // Bus handling
          switch (dataBusIn) {
            case DATA_IN_REG_B: dataBus = b; break; 
            case DATA_IN_REG_C: dataBus = c; break; 
            case DATA_IN_REG_D: dataBus = d; break; 
            case DATA_IN_REG_E: dataBus = e; break;
            case DATA_IN_REG_XYh:
              switch (xyMode) {
                case XY_MODE_HL: dataBus = (hl >>> 8); break;
                case XY_MODE_IX: dataBus = (ix >>> 8); break;
                case XY_MODE_IY: dataBus = (iy >>> 8); break;
              }
              break;
            case DATA_IN_REG_XYl:
              switch (xyMode) {
                case XY_MODE_HL: dataBus = hl; break;
                case XY_MODE_IX: dataBus = ix; break;
                case XY_MODE_IY: dataBus = iy; break;
              }
              break;
            case DATA_IN_REG_F: dataBus = dataBusSpMode ? sp >>> 8 : flags; break;
            case DATA_IN_REG_A: dataBus = dataBusSpMode ? sp : acc; break;
            case DATA_IN_REG_TMP: dataBus = tmpData; break;
            case DATA_IN_DATA: dataBus = DATA; break;
            case DATA_IN_ALU: dataBus = aluOut; break;
          }
          
          switch (dataBusOut) {
            case DATA_OUT_REG_B: b = dataBus; break;
            case DATA_OUT_REG_C: c = dataBus; break;
            case DATA_OUT_REG_D: d = dataBus; break;
            case DATA_OUT_REG_E: e = dataBus; break;
            case DATA_OUT_REG_XYh: 
              switch (xyMode) {
                case XY_MODE_HL: hl = (hl & 0x00FF) | (dataBus << 8) ; break;
                case XY_MODE_IX: ix = (ix & 0x00FF) | (dataBus << 8) ; break;
                case XY_MODE_IY: iy = (iy & 0x00FF) | (dataBus << 8) ; break;
              }
              break;
            case DATA_OUT_REG_XYl:
              switch (xyMode) {
                case XY_MODE_HL: hl = (hl & 0xFF00) | (dataBus & 0xFF); break;
                case XY_MODE_IX: ix = (ix & 0xFF00) | (dataBus & 0xFF); break;
                case XY_MODE_IY: iy = (iy & 0xFF00) | (dataBus & 0xFF); break;
              }
              break;
            case DATA_OUT_REG_F:
              if (dataBusSpMode) {
                sp = (sp & 0x00FF) | (dataBus << 8) ; break;
              } else {
                flags = dataBus; break;
              }
            case DATA_OUT_REG_A: 
              if (dataBusSpMode) {
                sp = (sp & 0xFF00) | (dataBus & 0xFF); break;
              } else {
                acc = dataBus; break;
              }
            case DATA_OUT_REG_W: wz = (wz & 0x00FF) | (dataBus << 8); break;
            case DATA_OUT_REG_Z: wz = (wz & 0xFF00) | (dataBus & 0xFF); break;
            case DATA_OUT_REG_TMP: tmpData = dataBus; break;
            case DATA_OUT_REG_TMP_ACC: tmpAcc = dataBus; break;
            case DATA_OUT_BUFFER : dataBuffer = dataBus; break;
          }
          
          switch (addrBusIn) {
            case ADDR_IN_REG_BC: addrBus = (b << 8) | (c & 0xFF); break;
            case ADDR_IN_REG_DE: addrBus = (d << 8) | (e & 0xFF); break;
            case ADDR_IN_REG_HL: 
              switch (xyMode) {
                case XY_MODE_HL: addrBus = hl; break;
                case XY_MODE_IX: addrBus = ix; break;
                case XY_MODE_IY: addrBus = iy; break;
              }
              break;
            case ADDR_IN_REG_SP: addrBus = sp; break;
            case ADDR_IN_REG_PC: addrBus = pc; break;
            case ADDR_IN_REG_WZ: addrBus = wz; break;
            case ADDR_IN_REG_IR: addrBus = ir; break;
            case ADDR_IN_REG_TMP: addrBus = tmpAddr; break;
          }

          switch (addrBusIncMode) {
            case ADDR_INC_MODE_INC: addrBusInc = addrBus + 1; break; 
            case ADDR_INC_MODE_DEC: addrBusInc = addrBus - 1; break;
            default: addrBusInc = addrBus; break;
          }
          
          switch (addrBusOut) {
            case ADDR_OUT_REG_BC: b = addrBusInc >> 8; c = addrBusInc; break;
            case ADDR_OUT_REG_DE: d = addrBusInc >> 8; e = addrBusInc; break;
            case ADDR_OUT_REG_HL: 
              switch (xyMode) {
                case XY_MODE_HL: hl = addrBusInc; break;
                case XY_MODE_IX: ix = addrBusInc; break;
                case XY_MODE_IY: iy = addrBusInc; break;
              }
            case ADDR_OUT_REG_SP: sp = addrBusInc; break;
            case ADDR_OUT_REG_PC: pc = addrBusInc; break;
            case ADDR_OUT_REG_WZ: wz = addrBusInc; break;
            case ADDR_OUT_REG_TMP: tmpAddr = addrBusInc; break;
            case ADDR_OUT_BUFFER: addrBuffer = addrBusInc; break;
          }
        } // if (RESET)
      } // if (CLK)

      lastClk = CLK;
    } // if (CLK != lastClk);
    
    // General output signals
    if (CLK) {
      MREQ = mreq;
      RD = rd;
      WR = wr;
      DATAZ = dataZ;
    } else {
      MREQ = mreqAlt;
      RD = rdAlt;
      WR = wrAlt;
      DATAZ = dataZAlt;
    }
    ADDR = addrBuffer;
    if (DATAZ) DATA = dataBuffer;
    M1 = m1;
    RFSH = rfsh;
  }
  
  public static void main(String[] args) throws Exception {
    Z80 z80 = new Z80();
    
    Oscilloscope osc = new Oscilloscope(
        Pin.of("CLK", z80, "CLK"),
        Pin.of("M1", z80, "M1"),
        Pin.of("RESET", z80, "RESET"),
        Pin.of("ADDR", z80, "ADDR"),
        Pin.of("DATA", z80, "DATA"),
        Pin.of("DATAZ", z80, "DATAZ"),
        Pin.of("MREQ", z80, "MREQ"),
        Pin.of("RD", z80, "RD"),
        Pin.of("WR", z80, "WR"),
        Pin.of("RFSH", z80, "RFSH")
    );
    
    for (int i = 0; i < 8 + 8*8; i++) {
      z80.CLK = (i & 0x01) == 0;
      z80.DATA = 0xC2;
      z80.RESET = i < 2;
      z80.updateEndPoints();
      osc.sample();
    }
    
    osc.print(1);
   
    final int CYCLES = 3500000;
    
    for (int i = 0; i < CYCLES * 2; i++) {
      z80.updateEndPoints();
      z80.CLK = !z80.CLK;
    }
    long start = System.currentTimeMillis();
    for (int i = 0; i < CYCLES * 2; i++) {
      z80.updateEndPoints();
      z80.CLK = !z80.CLK;
    }
    System.out.println("Completed " + CYCLES + " cycles in: " + 
        ((System.currentTimeMillis() - start)) + "ms");
  }
}
